#include "http/my_http_request.h"
#include "config/my_cache_conf.h"
#include "event/my_event_loop.h"
#include "event/my_tcp.h"
#include "event/my_worker.h"
#include "event/my_event_loop.h"
#include "comm/my_inet.h"
#include "comm/my_cleanup.h"
#include "comm/my_hooks.h"
#include "comm/my_handlers.h"
#include <signal.h>

void my_test_server_accept_handler(my_event_t *event)
{
    my_conn_t *server_conn = event->data;

    MY_LOG_DEBUG("server conn fd: %d", server_conn->fd);

    my_http_request_t *request = my_http_request_create(server_conn);
    server_conn->read.handler = my_http_read_request_header_handler;
    server_conn->data = request;

    my_event_loop_start_io(server_conn);
    // enabled read
    my_event_loop_enabled_io(server_conn, MY_FALSE);
}

const char *str1 = "GET /1.txt?a=1&b=2&c=3#frag HTTP/1.1\r\n";
const char *str2 = "Host: www.a.com\r\n";
const char *str3 = "HeaderA: aaaa\r\n";
const char *str4 = "HeaderB: bbbb\r\n";
const char *str5 = "Content-Length: 18\r\n";
const char *str6 = "\r\n";
const char *str7 = "Hello, i am client";

void my_test_client_write_handler(my_event_t *event)
{
    my_conn_t *client_conn = event->data;
    int step = (int)(intptr_t)client_conn->data;

    if (event->timedout) {
        MY_LOG_DEBUG("test_client: timer timeout");
        /* enabled write io */
        my_event_loop_enabled_io(client_conn, MY_TRUE);
        event->timedout = 0;
        return;
    }

    step++;

    if (step > 8) {
        MY_LOG_DEBUG("test_client: send request done");
        my_event_loop_remove_conn(client_conn);
        my_conn_close(client_conn);
        my_thread_free(client_conn);
        return;
    }

    const char *str;

    switch(step) {
    case 1:
        str = str1;
        break;
    case 2:
        str = str2;
        break;
    case 3:
        str = str3;
        break;
    case 4:
        str = str4;
        break;
    case 5:
        str = str5;
        break;
    case 6:
        str = str6;
        break;
    case 7:
        str = str7;
        break;
    case 8:
        str = str6;
        break;
    }

    my_syscall_result_t res;
    res = my_conn_write(client_conn, (void *)str, (int64_t)strlen(str));

    if (res.res != (int64_t)strlen(str)) {
        MY_LOG_FATAL("test_client send failed, except: %d, but got %d, errno: %d",
            strlen(str), res.res);
    }

    // disabled write
    my_event_loop_disabled_io(client_conn, MY_TRUE);
    client_conn->data = (void *)(intptr_t)step;
    my_event_loop_add_timer(event, 1000);
}

const char *str8 = "Transfer-Encoding: chunked\r\n";

void my_test_client_chunk_write_handler(my_event_t *event)
{
    my_conn_t *client_conn = event->data;
    int step = (int)(intptr_t)client_conn->data;

    if (event->timedout) {
        MY_LOG_DEBUG("test_client: timer timeout");
        /* enabled write io */
        my_event_loop_enabled_io(client_conn, MY_TRUE);
        event->timedout = 0;
        return;
    }

    step++;

    if (step > 10) {
        MY_LOG_DEBUG("test_client: send request done");
        my_event_loop_remove_conn(client_conn);
        my_conn_close(client_conn);
        my_thread_free(client_conn);
        return;
    }

    const char *str;

    switch(step) {
    case 1:
        str = str1;
        break;
    case 2:
        str = str2;
        break;
    case 3:
        str = str3;
        break;
    case 4:
        str = str4;
        break;
    case 5:
        str = str8;
        break;
    case 6:
        str = str6;
        break;
    case 7:
        str = "12\r\n";
        break;
    case 8:
        str = str7;
        break;
    case 9:
        str = str6;
        break;
    case 10:
        str = "0\r\n\r\n";
        break;
    }

    my_syscall_result_t res;
    res = my_conn_write(client_conn, (void *)str, (int64_t)strlen(str));

    if (res.res != (int64_t)strlen(str)) {
        MY_LOG_FATAL("test_client send failed, except: %d, but got %d, errno: %d",
            strlen(str), res.res);
    }

    // disabled write
    my_event_loop_disabled_io(client_conn, MY_TRUE);
    client_conn->data = (void *)(intptr_t)step;
    my_event_loop_add_timer(event, 100);
}


void my_test_client_connect_handler(void *data, my_conn_t *conn, my_result_t res)
{
    if (res != MY_SUCCESS) {
        return;
    }

    MY_LOG_DEBUG("client conn fd: %d", conn->fd);

    conn->data = (void *)0;

    if ((intptr_t)data == 1) {
        conn->write.handler = my_test_client_chunk_write_handler;
    } else {
        conn->write.handler = my_test_client_write_handler;
    }

    my_event_loop_start_io(conn);
    my_event_loop_enabled_io(conn, MY_TRUE);
}

void my_test_client_handler(my_dispatch_event_t *dispatch_event)
{
    void *data = dispatch_event->data;
    my_thread_free(dispatch_event);

    my_address_t address;
    bzero(&address, sizeof(my_address_t));
    if (!my_address_parser("127.0.0.1:9999", &address, NULL)) {
        MY_LOG_FATAL("parse %s failed", "127.0.0.1:9999");
    }

    my_tcp_connect_opts_t opts = {
        .nodelay = 1,
    };

    MY_LOG_DEBUG("test_client setup connect");
    my_tcp_connect_setup(&address, 10, &opts,
        my_test_client_connect_handler, data);
}

void my_http_do_request(my_bool_t use_chunk)
{
    my_dispatch_event_t *dispatch_event =
        my_thread_calloc(sizeof(my_dispatch_event_t));
    if (use_chunk) {
        dispatch_event->data = (void *)1;
    }
    dispatch_event->handler = my_test_client_handler;
    my_worker_processor_post_event(dispatch_event);
}

int main()
{
    signal(SIGPIPE,NULL);

    my_hooks_init();
    my_cache_conf_init();

    my_tcp_listener_t *ls = my_thread_calloc(sizeof(my_tcp_listener_t));
    my_cleanup_handler_add(&g_cleanup_list, my_thread_free_handle, ls);
    my_tcp_listener_init(ls);
    ls->listen_addr = "127.0.0.1:9999";
    ls->backlog = 1024;
    ls->nodelay = 1;
    ls->reuseaddr = 1;
    ls->accept_handler = my_test_server_accept_handler;

    my_tcp_listener_register(ls);

    my_worker_processor_start();
    my_tcp_accepter_start();

    my_http_do_request(MY_FALSE);
    my_http_do_request(MY_TRUE);
    sleep(2);

    my_tcp_accepter_stop();
    my_worker_processor_stop();

    my_cleanup_list_exec(&g_cleanup_list);
    my_mem_assert_leak();

    MY_LOG_DEBUG("test pass");
    return 0;
}